package com.gitee.kamismile.gateway.component.filter;

import com.gitee.kamismile.gateway.component.route.RedisRouteDefinitionRepository;
import com.gitee.kamismile.stone.commmon.base.ResultVO;
import com.gitee.kamismile.stone.commmon.util.JsonUtil;
import com.gitee.kamismile.stone.commmon.util.ValueUtils;
import com.gitee.kamismile.stoneComEx.common.filter.server.ReactiveFilter;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.springframework.http.server.PathContainer.parsePath;

@Component
public class VersionServiceReactiveFilter implements ReactiveFilter {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    String deafultVersion = "0.0.0";

    @Autowired
    @Lazy
    RedisRouteDefinitionRepository redisRouteDefinitionRepository;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        AtomicReference<ServerHttpRequest> build = new AtomicReference<>(exchange.getRequest());

        String versionInfo = exchange.getRequest().getHeaders().getFirst("versionInfo");

        PathContainer path = parsePath(exchange.getRequest().getURI().getPath());

        AtomicReference<String> versionFlag = new AtomicReference<>("0");

        redisRouteDefinitionRepository.getRouteDefinitions().collectList()
                .subscribeOn(Schedulers.boundedElastic())
                .subscribe(routeDefinition -> {
            List<RouteDefinition> list = getCollectVersionSort(path, routeDefinition);
            if (CollectionUtils.isEmpty(list)) {
                return;
            }

            if (ValueUtils.isNull(versionInfo) || deafultVersion.equals(versionInfo)) {
                Optional<RouteDefinition> routeDefinitionInfo = list.stream().findFirst();
                routeDefinitionInfo.ifPresent(v -> {
                    build.set(exchange.getRequest().mutate()
                            .header("versionInfo", v.getPredicates().stream()
                                    .filter(p -> "Header".equals(p.getName()))
                                    .filter(p -> p.getArgs().get("header").equals("versionInfo"))
                                    .findFirst()
                                    .map(f -> f.getArgs().get("regexp")).get())
                            .build());
                });
            } else {
                String[] versions = versionInfo.split("\\.");

                Optional<RouteDefinition> routeDefinitionInfo = list.stream()
                        .filter(v -> CollectionUtils.isNotEmpty(v.getPredicates().stream()
                                .filter(p -> "Header".equals(p.getName()))
                                .filter(p -> p.getArgs().get("header").equals("versionInfo"))
                                .filter(p -> versionInfo.equals(p.getArgs().get("regexp"))).collect(Collectors.toList())))
                        .collect(Collectors.toList()).stream().findFirst();

                if(!routeDefinitionInfo.isPresent()) {
                    Supplier<Stream<RouteDefinition>> routeDefinitionVersionInfo = () -> list.stream()
                            .filter(v -> CollectionUtils.isNotEmpty(v.getPredicates().stream()
                                    .filter(p -> "Header".equals(p.getName()))
                                    .filter(p -> p.getArgs().get("header").equals("versionInfo"))
                                    .collect(Collectors.toList())));

                    Supplier<Stream<RouteDefinition>> mainVersion = () -> routeDefinitionVersionInfo.get()
                            .filter(v -> CollectionUtils.isNotEmpty(v.getPredicates()
                                    .stream()
                                    .filter(p -> "Header".equals(p.getName()))
                                    .filter(p -> {
                                        String[] infoVersion = p.getArgs().getOrDefault("regexp", deafultVersion).split("\\.");
                                        return versions[0].equals(infoVersion[0]);
                                    })
                                    .collect(Collectors.toList())));

                    if (CollectionUtils.isEmpty(mainVersion.get().collect(Collectors.toList()))) {
                        versionFlag.set("1");
                        return;
                    }

                    mainVersion.get().findFirst()
                            .ifPresent(v -> {
                                build.set(exchange.getRequest().mutate()
                                        .headers(h -> h.remove("versionInfo"))
                                        .header("versionInfo", v.getPredicates().stream()
                                                .filter(p -> "Header".equals(p.getName()))
                                                .filter(p -> p.getArgs().get("header").equals("versionInfo"))
                                                .findFirst()
                                                .map(f -> f.getArgs().get("regexp")).get())
                                        .build());
                            });

                };
            }
        });

        if (versionFlag.get().equals("1")) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UPGRADE_REQUIRED);
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

            ResultVO resultVO = new ResultVO();
            resultVO.setCode(HttpStatus.UPGRADE_REQUIRED.toString());
            resultVO.setMessage("版本太低，需要升级");
            return response.writeWith(
                    Mono.just(response.bufferFactory().wrap(JsonUtil.toJson(resultVO).getBytes()))
                    .subscribeOn(Schedulers.boundedElastic())
            );
        }
        return chain.filter(exchange.mutate().request(build.get()).build());
    }

    private List<RouteDefinition> getCollectVersionSort(PathContainer path, List<RouteDefinition> routeDefinition) {
        return routeDefinition.stream()
                .filter(route ->
                        CollectionUtils.isNotEmpty(route.getPredicates().stream()
                                .filter(v -> "Path".equals(v.getName()))
                                .filter(v -> {
                                    PathPatternParser pathPatternParser = new PathPatternParser();
                                    PathPattern pathPattern = pathPatternParser.parse(v.getArgs().get("pattern"));
                                    return pathPattern.matches(path);
                                }).collect(Collectors.toList()))).sorted((o1, o2) -> {

                    AtomicReference<String> version1 = new AtomicReference<>(deafultVersion);
                    AtomicReference<String> version2 = new AtomicReference<>(deafultVersion);

                    o1.getPredicates().stream().filter(v -> "Header".equals(v.getName()))
                            .filter(v -> v.getArgs().get("header").equals("versionInfo"))
                            .findFirst().ifPresent(v -> {
                        version1.set(v.getArgs().get("regexp"));
                    });

                    o2.getPredicates().stream().filter(v -> "Header".equals(v.getName()))
                            .filter(v -> v.getArgs().get("header").equals("versionInfo"))
                            .findFirst().ifPresent(v -> version2.set(v.getArgs().get("regexp")));

                    String[] versions1 = version1.get().split("\\.");
                    String[] versions2 = version2.get().split("\\.");

                    if (ValueUtils.isLongNull(versions2[0]).compareTo(ValueUtils.isLongNull(versions1[0])) != 0) {
                        return ValueUtils.isLongNull(versions2[0]).compareTo(ValueUtils.isLongNull(versions1[0]));
                    } else if (ValueUtils.isLongNull(versions2[1]).compareTo(ValueUtils.isLongNull(versions1[1])) != 0) {
                        return ValueUtils.isLongNull(versions2[1]).compareTo(ValueUtils.isLongNull(versions1[1]));
                    } else if (ValueUtils.isLongNull(versions2[2]).compareTo(ValueUtils.isLongNull(versions1[2])) != 0) {
                        return ValueUtils.isLongNull(versions2[2]).compareTo(ValueUtils.isLongNull(versions1[2]));
                    }

                    return 0;
                }).collect(Collectors.toList());
    }

}
